#!/usr/bin/env python
'''PyLibrarian web administration interface'''

import ConfigParser
import math
import database
import model
import urllib2
import traceback
import codecs
import pdfGen
import os

from bottle import *
from xml.dom import minidom
from beaker.middleware import SessionMiddleware

try:
  import json
except ImportError:
  import simplejson as json

config = ConfigParser.ConfigParser()
config.readfp(open('config.ini'))

libraryName = config.get('library', 'name')
accessKey = config.get('library', 'isbndb_access_key')
paging = config.getint('library', 'paging')
server_port = config.get('backend', 'server_port')

app = default_app()
session_opts = {
  'session.type': 'file',
  'session.cookie_expires': 300,
  'session.data_dir': os.path.join(os.path.dirname(__file__), 'data')
}
app = SessionMiddleware(app, session_opts)

#
# Page handlers
#

@route('/')
def index():
  '''Web access front page'''
  numResources = len(database.getResources())
  popularResources = database.getPopularResources()
  resourceTypes = dict([(res['id'], res['description']) for res in database.getResourceTypes()])
  resourceAvailability = dict((resource['id'], True if database.isResourceCheckedIn(resource['id']) else False) for resource in popularResources)
  
  return template('index', libraryName=libraryName, numResources=numResources,
                  popularResources=popularResources,
                  resourceAvailability=resourceAvailability,
                  resourceTypes=resourceTypes)

@route('/search')
def search():
  '''Resource search results page'''
  searchText = request.GET.get('search_text', '').strip()
  page = int(request.GET.get('page', 1))
  asc = int(request.GET.get('asc', 0))
  sortColumn = eval('model.Resource.%s' % request.GET.get('sort', '')) \
                    if hasattr(model.Resource, request.GET.get('sort', '')) \
                    else None
  
  query = database.searchResources(searchText)
  numResults = query.count()
  
  if sortColumn:
    query = query.order_by(sortColumn.asc()) if asc else query.order_by(sortColumn.desc())
  
  resources = [result.__dict__ for result in query
                                             .limit(paging)
                                             .offset((page - 1) * paging)
                                             .all()]
  
  resourceTypes = dict([(res['id'], res['description']) for res in database.getResourceTypes()])
  resourceAvailability = dict((resource['id'], True if database.isResourceCheckedIn(resource['id']) else False) for resource in resources)
  
  return template('search', libraryName=libraryName, searchText=searchText,
                  resources=resources, resourceTypes=resourceTypes,
                  resourceAvailability=resourceAvailability, page=page,
                  numPages=int(math.ceil(numResults / float(paging))),
                  numResults=numResults, paging=paging,
                  sortColumn=request.GET.get('sort', ''), asc=asc)

@route('/administration')
def admin():
  '''Administration home page'''
  if not check_admin_login(request):
    redirect('/administration/login')
  
  return template('admin', libraryName=libraryName, message=request.GET.get('result', ''))
  
@route('/administration/addResourceStep2', method='POST')
def addResourceStep2():
  '''Page for filling out information for a new resource'''
  if not check_admin_login(request):
    redirect('/administration/login')
  
  error = False
  isbn = request.POST.get('isbn')
  data = lookupISBN(isbn)
  resourceTypes = database.getResourceTypes()
  
  if not data:
    error = True
    data = {'isbn': isbn}
  if data.get('inDB'):
    database.addResource(data.get('isbn'), data.get('resource_type_id'), data.get('title'), data.get('author'),
                         data.get('publisher'), data.get('summary'))
    redirect('/administration/manageResources?message=Resource+added+automatically+since+we+already+have+another+copy+of+it')
  else:
    return template('addResourceStep2', libraryName=libraryName, data=data,
                    resourceTypes=resourceTypes, error=error,
                    noISBN=isbn.strip() == '')
  

@route('/administration/addResource', method='POST')
def addResource():
  '''Adds a resource to the database'''
  if not check_admin_login(request):
    redirect('/administration/login')
  
  isbn = request.POST.get('isbn')
  title = request.POST.get('title')
  author = request.POST.get('author')
  publisher = request.POST.get('publisher')
  resource_type_id = request.POST.get('resource_type_id')
  summary = request.POST.get('summary')
  
  database.addResource(isbn, resource_type_id, title, author, publisher, summary)
  
  redirect('/administration/manageResources?message=Success%21')
  
@route('/administration/deleteResource/:id')
def deleteResource(id):
  '''Removes a resource from the database'''
  if not check_admin_login(request):
    redirect('/administration/login')
  
  database.deleteResourceByID(id)
  
  redirect('/administration/manageResources?message=Resource%20deleted')
  
@route('/administration/manageResources')
def manageResources():
  '''Page listing resources and management options'''
  if not check_admin_login(request):
    redirect('/administration/login')
  
  resourceTypes = dict([(res['id'], res['description']) for res in database.getResourceTypes()])
  resources = database.getResources()
  resourceAvailability = dict((resource['id'], True if database.isResourceCheckedIn(resource['id']) else False) for resource in resources)
  message = request.GET.get('message', '')
  
  return template('manageResources', libraryName=libraryName, resources=resources,
                  resourceTypes=resourceTypes, message=message, resourceAvailability=resourceAvailability)
  
@route('/administration/manageResource/:id')
def manageResource(id):
  '''Page for managing a resource'''
  if not check_admin_login(request):
    redirect('/administration/login')
    
  resource = database.getResourceByID(id)
  resourceTypes = database.getResourceTypes()
  
  return template('manageResource', libraryName=libraryName, data=resource,
                  resourceTypes=resourceTypes)

@route('/administration/checkedOut')
def checkedOutResources():
  '''Page that lists checked out resources'''
  
  try:
    checkIn = request.GET.get('checkIn')
    if checkIn and checkIn.isdigit():
      database.checkinResource(int(checkIn))
    
    resources = database.getCheckedOutResources()
    resourceTypes = dict([(res['id'], res['description']) for res in database.getResourceTypes()])
    return template('checkedOut', libraryName=libraryName, resources=resources,
                    resourceTypes=resourceTypes, overdue=False)
  except:
    print traceback.format_exc()
    raise
  
@route('/administration/overdue')
def overdueResources():
  '''Page that lists overdue resources'''
  
  try:
    resources = database.getCheckedOutResources(overdue=True)
    resourceTypes = dict([(res['id'], res['description']) for res in database.getResourceTypes()])
    return template('checkedOut', libraryName=libraryName, resources=resources,
                    resourceTypes=resourceTypes, overdue=True)
  except:
    print traceback.format_exc()
    raise
  
@route('/administration/updateResource/:isbn', method='POST')
def updateResource(isbn):
  '''Updates a resource's database record'''
  if not check_admin_login(request):
    redirect('/administration/login')
    
  title = request.POST.get('title')
  author = request.POST.get('author')
  publisher = request.POST.get('publisher')
  resource_type_id = request.POST.get('resource_type_id')
  summary = request.POST.get('summary')
  if isbn != 'NO ISBN':
    database.updateResourcesByISBN(isbn, resource_type_id, title, author, publisher, summary)
  else:
    database.updateResource(request.POST.get('id'), resource_type_id, title, author, publisher, summary)
  
  redirect('/administration/manageResources?message=Resource%20updated')
  
@route('/administration/addUser', method='POST')
def addUser():
  '''Adds a user to the system'''
  if not check_admin_login(request):
    redirect('/administration/login')
  
  name = request.POST.get('name')
  username = request.POST.get('username')
  password = request.POST.get('password')
  street = request.POST.get('street')
  city = request.POST.get('city')
  state = request.POST.get('state')
  zip = request.POST.get('zip')
  phone = request.POST.get('phone')
  email = request.POST.get('email')
  
  database.addUser(name, username, password, street, city, state, zip, phone, email)
  
  redirect('/administration/manageUsers?message=Success%21')
  
@route('/administration/manageUsers')
def manageUsers():
  '''Page listing users with management options'''
  if not check_admin_login(request):
    redirect('/administration/login')
  
  users = database.getUsers()
  message = request.GET.get('message', '')
  return template('manageUsers', libraryName=libraryName, users=users,
                  message=message)
  
@route('/administration/manageUser/:id')
def manageUser(id):
  '''Page for managing user information'''
  if not check_admin_login(request):
    redirect('/administration/login')
    
  user = database.getUserByID(id)
  resourceTypes = database.getResourceTypes()
  
  return template('manageUser', libraryName=libraryName, data=user,
                  resourceTypes=resourceTypes)
  
@route('/administration/updateUser/:id', method='POST')
def updateUser(id):
  '''Updates user information'''
  if not check_admin_login(request):
    redirect('/administration/login')
    
  name = request.POST.get('name')
  username = request.POST.get('username')
  password = request.POST.get('password')
  street = request.POST.get('street')
  city = request.POST.get('city')
  state = request.POST.get('state')
  zip = request.POST.get('zip')
  phone = request.POST.get('phone')
  email = request.POST.get('email')
  
  database.updateUser(id, name, username, password, street, city, state, zip, phone, email)
  
  redirect('/administration/manageUsers?message=User%20updated')
  
@route('/administration/deleteUser/:id')
def deleteUser(id):
  '''Removes a user from the system'''
  if not check_admin_login(request):
    redirect('/administration/login')
  
  database.deleteUser(id)
  
  redirect('/administration/manageUsers?message=User%20deleted')
  
@route('/administration/printLabels')
def printLabels():
  '''Book selection page for label printing'''
  if not check_admin_login(request):
    redirect('/administration/login')
  
  resourceTypes = dict([(res['id'], res['description']) for res in database.getResourceTypes()])
  resources = database.getResources()
  resourceAvailability = dict((resource['id'], True if database.isResourceCheckedIn(resource['id']) else False) for resource in resources)
  
  return template('printLabels', libraryName=libraryName, resources=resources,
                  resourceTypes=resourceTypes)
  
@route('/administration/generateLabels.pdf', method='GET')
def generateLabels():
  '''Label generation page - serves a dynamically generated PDF with printable
     barcodes for the requested books'''
  if not check_admin_login(request):
    redirect('/administration/login')
  
  ids = json.loads(request.GET.get('ids'))
  response.content_type = 'application/pdf'
  return pdfGen.generateLabels(ids)
  
@route('/administration/printCards')
def printCards():
  '''Book selection page for label printing'''
  if not check_admin_login(request):
    redirect('/administration/login')
  
  users = database.getUsers()
  
  return template('printCards', libraryName=libraryName, users=users)
  
@route('/administration/generateCards.pdf', method='GET')
def generateCards():
  '''Library Card generation page - serves a dynamically generated PDF with printable
     barcodes for the requested users'''
  if not check_admin_login(request):
    redirect('/administration/login')
  
  ids = json.loads(request.GET.get('ids'))
  response.content_type = 'application/pdf'
  return pdfGen.generateCards(ids)
  
@route('/administration/generateLibrarianCardSheet.pdf', method='GET')
def generateCardSheet():
  '''Library Card generation page - serves a dynamically generated PDF with printable
     barcodes for the requested users'''
  if not check_admin_login(request):
    redirect('/administration/login')
  
  ids = json.loads(request.GET.get('ids'))
  response.content_type = 'application/pdf'
  return pdfGen.generateLibrarianCardholderSheet(ids)
  
@route('/administration/login')
def adminLogin():
  '''Login page'''
  error = False
  
  if request.GET.get('pylibrary_username') and check_admin_login(request, request.GET.get('pylibrary_username'), request.GET.get('pylibrary_password')):
    redirect('/administration')
  elif request.GET.get('pylibrary_username')  or request.GET.get('pylibrary_password') :
    error = True
    
  return template('login', libraryName=libraryName, error=error)
  
@route('/logout')
def logout():
  '''Logout page'''
  s = request.environ.get('beaker.session')
  s['admin_login'] = False
  s['logged_in'] = False
  s.save()
  redirect('/')
  
#
# Static file handlers
#

@route('/css/:filename#.*\.css#')
def static_css(filename):
  '''CSS static file handler'''
  send_file(filename, root='css', mimetype='text/css')

@route('/js/:filename#.*\.js#')
def static_js(filename):
  '''JavaScript static file handler'''
  send_file(filename, root='js', mimetype='text/javascript')

@route('/images/:filename#.*\.gif#')
def static_gifs(filename):
  '''GIF static file handler'''
  send_file(filename, root='images', mimetype='image/gif')

#
# Utility functions
#

def check_admin_login(request, username=None, password=None):
  '''Checks a user's login credentials to ensure that the are 1) valid, and 2) they
     are for an administrator account'''
  
  s = request.environ.get('beaker.session')
  if s.get('admin_login'):
    return True
  
  try:
    if database.checkLogin(username, password).admin:
      s['admin_login'] = True
      s['logged_in'] = True
      s.save()
      return True
    else:
      return False
  except:
    return False

def lookupISBN(isbn):
  '''Looks up information on an ISBN number from the local database first, and then
     isbndb.com if it is a new book to our system (not in the db)'''
  try:
    results = database.getResourcesByISBN(isbn)
    if results:
      result = results[0]
      title = result['title']
      author = result['author']
      publisher = result['publisher']
      isbn = result['isbn']
      resource_type_id = result['resource_type_id']
      summary = result['summary']
      inDB = True
    elif len(isbn) == 10 or len(isbn) == 13:    # Save API calls when we scan a non-ISBN
      stream = urllib2.urlopen('http://isbndb.com/api/books.xml?access_key=%s&results=texts&index1=isbn&value1=%s' % (accessKey, isbn))
      result = stream.read()
      stream.close()
      xmlDoc = minidom.parseString(result)
      resourceData = xmlDoc.getElementsByTagName('BookList')[0].getElementsByTagName('BookData')[0]
      
      title = resourceData.getElementsByTagName('Title')[0].firstChild.nodeValue.strip()
      author = resourceData.getElementsByTagName('AuthorsText')[0].firstChild.nodeValue.strip().rstrip(',')
      publisher = resourceData.getElementsByTagName('PublisherText')[0].firstChild.nodeValue.strip()
      isbn = resourceData.getAttribute('isbn13') if resourceData.hasAttribute('isbn13') else resourceData.getAttribute('isbn')
      try:
        summary = resourceData.getElementsByTagName('Summary')[0].firstChild.nodeValue.strip()
      except:
        print 'error getting summary:', traceback.format_exc()
        summary = ''
      inDB = False
      resource_type_id = None
    else:
      return {}
    
    return {'title': title, 'author': author, 'publisher': publisher, 'summary': summary,
            'isbn': isbn, 'resource_type_id': resource_type_id, 'inDB': inDB}
  except:
    print traceback.format_exc()
    return {}

if __name__ == '__main__':
  run(app=app, host='0.0.0.0', port=server_port)
